" Author: Radoslaw Burny (rburny@google.com)
        "
        " Copyright 2015 Google Inc. All rights reserved.
        "
        " Licensed under the Apache License, Version 2.0 (the "License");
        " you may not use this file except in compliance with the License.
        " You may obtain a copy of the License at

        "     http://www.apache.org/licenses/LICENSE-2.0
"
        " Unless required by applicable law or agreed to in writing, software
        " distributed under the License is distributed on an "AS IS" BASIS,
        " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        " See the License for the specific language governing permissions and
        " limitations under the License.


if exists('g:loaded_searchIdx')
    finish
en
let g:loaded_searchIdx = 1

" Setup options.
    if !exists('g:searchIdx_line_limit')
        let g:searchIdx_line_limit = 1000000
    en

    if !exists('g:searchIdx_improved_star')
        let g:searchIdx_improved_star = 1
    en

    if !exists('g:searchIdx_star_case')
        let g:searchIdx_star_case = 1
    en

"\ set shortmess+=s
    " Suppress the "search hit BOTTOM, continuing at TOP" type messages.

com!  -bar SearchIdx call <SID>PrintMatches()
    " show search index of last search pattern at
    " the current cursor position.

    sil! nmap <unique> g/ <Plug>SearchIdx
              " If user has mapped 'g/', don't override it.

    no            <Plug>SearchIdx   <Nop>
    no!           <Plug>SearchIdx   <Nop>
    nno  <silent> <Plug>SearchIdx   :call <SID>PrintMatches()<CR>

" Remap search commands (only if they're not mapped by the user).
"\ 往上面搜时, 到了top会有warn
"\ 但往下搜时, 到了bottom没有warn
    sil! nmap <silent><unique>  n      n<Plug>SearchIdx
    sil! nmap <silent><unique>  N      N<Plug>SearchIdx

    sil! map          <unique>  *       <Plug>ImprovedStar_*<Plug>SearchIdx
    sil! map          <unique>  #       <Plug>ImprovedStar_#<Plug>SearchIdx
    sil! map          <unique>  g*      <Plug>ImprovedStar_g*<Plug>SearchIdx
    sil! map          <unique>  g#      <Plug>ImprovedStar_g#<Plug>SearchIdx

    no  <silent><expr>  <Plug>ImprovedStar_*  <SID>StarSearch('*')
    no  <silent><expr>  <Plug>ImprovedStar_#  <SID>StarSearch('#')
    no  <silent><expr>  <Plug>ImprovedStar_g* <SID>StarSearch('g*')
    no  <silent><expr>  <Plug>ImprovedStar_g# <SID>StarSearch('g#')

sil! cmap <unique><expr> <CR>
        \ "\<CR>" . (getcmdtype() =~ '[/?]'
                        \ ? "<Plug>SearchIdx"
                        \ : "" )
    " Remap searches from '/' and 'q/' by plugging into <CR> in cmdline & cmdwin.
    "\ This cannot use <silent>
        " it would break cmdline refresh in some  cases (e.g. multiline commands, <C-R>= usage).
    "\ If use a helper method
        " it will breaks debug mode  (issue #14).
        " Consider reimplementing it based on CmdlineEnter and  CmdlineLeave events to make it less intrusive.


aug  searchIdx_cmdwin
    au!
    "\ 上面是cmap, 这里是nmap
    au CmdWinEnter *
        \ if getcmdwintype() =~ '[/?]' |
        \   silent! nmap <buffer><unique>  <CR>    <CR><Plug>SearchIdx|
        \ endif
aug  END

" Implementation details.

    fun! s:StarSearch(cmd)
        if !g:searchIdx_improved_star | return a:cmd | endif

        " With no word under cursor, search will fail. Fall back to '*' so that
        " error seems to come from native Vim command, not from this function.
        if expand("<cword>") == "" | return "*" | endif

        " reimplement star commands using '/' and '?'
        let search_dir = (a:cmd == '*' || a:cmd == 'g*') ? '/' : '?'
        let case_char = (g:searchIdx_star_case ? '\C' : '\c')
        let [open_delim, close_delim] = (a:cmd =~ 'g.' ? ['', ''] : ['\<', '\>'])
        let search_term = open_delim . "\<C-R>\<C-W>" . close_delim
        return search_dir . search_term . case_char . "\<CR>"
    endf

    fun! s:MatchesInRange(range)
        " Use :s///n to search efficiently in large files. Although calling search()
        " in the loop would be cleaner (see issue #18), it is also much slower.
        let gflag = &gdefault ? '' : 'g'
        let saved_marks = [ getpos("'["), getpos("']") ]
        let output = ''
        redir => output
            sil! execute 'keepjumps ' . a:range . 's//~/en' . gflag
        redir END
        call setpos("'[", saved_marks[0])
        call setpos("']", saved_marks[1])
        return str2nr(matchstr(output, '\d\+'))
    endf

    " Calculate which match in the current line the 'col' is at.
    fun! s:MatchInLine()
        let line = line('.')
        let col = col('.')
        let star_search = 0

        norm! 0
        let matches = 0
        let s_opt = 'c'
        " The count might be off in edge cases (e.g. regexes that allow empty match,
        " like 'a*'). Unfortunately, Vim's searching functions are so inconsistent
        " that I can't fix this.
        while search(@/, s_opt, line) && col('.') <= col
            let matches += 1
            let s_opt = ''
        endwhile

        return matches
    endf

    "\ Efficiently recalculate number of matches above cursor using values cached
    "\ from the previous run.
    fun! s:MatchesAbove(cached_values)
        " avoid wrapping range at the beginning of file
        if line('.') == 1 | return 0 | endif

        let [old_line, old_result, total] = a:cached_values
        " Find the nearest point from which we can restart match counting (top,
        " bottom, or previously cached line).
        let line = line('.')
        let to_top = line
        let to_old = abs(line - old_line)
        let to_bottom = line('$') - line
        let min_dist = min([to_top, to_old, to_bottom])

        if min_dist == to_top
            return s:MatchesInRange('1,.-1')
        elseif min_dist == to_bottom
            return total - s:MatchesInRange(',$')
        " otherwise, min_dist == to_old, we just need to check relative line order
        elseif old_line < line
            return old_result + s:MatchesInRange(old_line . ',-1')
        elseif old_line > line
            return old_result - s:MatchesInRange(',' . (old_line - 1))
        el " old_line == line
            return old_result
        en
    endf

    " Return the given string, shortened to the maximum length. The middle of the
    " string would be replaced by '...' in case the original string is too long.
    fun! s:ShortString(str, max_length)
        if len(a:str) < a:max_length  | return a:str  | en

        " Calculate the needed length of each part of the str.
        " The 3 is because the middle part would be replace with 3 points.
        let l:str_half_len = (a:max_length - 3) / 2

        let l:start = a:str[:l:str_half_len - 1]
        let l:end   = a:str[len(a:str) - l:str_half_len:]

        return  l:start . "..." . l:end
    endf

    fun! s:PrintMatches()
        let l:dir_char = v:searchforward
                    \ ? '↓↓ '
                    \ : '↑↑ '
        if line('$') > g:searchIdx_line_limit
            let l:msg = '[MAX]  ' . l:dir_char . @/
        el
            " If there are no matches, search fails before we get here. The only way
            " we could see zero results is on 'g/' (but that's a reasonable result).
            let [l:current, l:total] = searchIdx#MatchCounts()
            let l:msg = '[' . l:current . '/' . l:total . ']  ' . l:dir_char . @/
        en

        " foldopen+=search causes search commands to open folds in the matched line
        " but it doesn't work in mappings.
        " Hence, we just open the folds here.
        if &foldopen =~# "search"
            norm! zv
        en

        if &shortmess =~# "T"
            let l:msg = s:ShortString(l:msg, &columns - 11)
                " Shorten the message string,
                " to make it one screen wide.
                " It seems that the press enter message
                    "  won't be printed only if
                    "  the length  of the message is shorter by at least 11 chars than the real length of the  screen.
        en

        " Flush any delayed screen updates before printing "l:msg"
        " See ":h :echo-redraw".
        redraw | echo l:msg
    endf

    " Return 2-element array, containing current index and total number of matches
    " of @/ (last search pattern) in the current buffer.
    fun! searchIdx#MatchCounts()
        " both :s and search() modify cursor position
        let win_view = winsaveview()
        " folds affect range of ex commands (issue #4)
        let save_foldenable = &foldenable
        set nofoldenable

        let in_line = s:MatchInLine()

        let cache_key = [b:changedtick, @/]
        if exists('b:searchIdx_cache_key') &&
        \ b:searchIdx_cache_key ==# cache_key
            let before = s:MatchesAbove(b:searchIdx_cache_val)
            let total  = b:searchIdx_cache_val[-1]
        el
            let before = (line('.') == 1
                            \ ? 0
                            \ : s:MatchesInRange('1,-1') )
            let total = before + s:MatchesInRange(',$')
        en

        let b:searchIdx_cache_val = [line('.'), before, total]
        let b:searchIdx_cache_key = cache_key

        let &foldenable = save_foldenable
        call winrestview(win_view)

        return [before + in_line, total]
    endf

" IMPLEMENTATION NOTES

        " SEARCH TRIGGER
                " It's tricky to detect when search is done precisely.
                " We achieve this with  two-level mappings:
                        " * conditional mapping of <CR> in cmdline / cmdwin.
                          " It checks command type to
                        "   only add <Plug>SearchIdx after search,
                            "   not ex command or anything else.
                        " * mode-specific remappings of said <Plug> command that only display search
                        "   index in normal mode
                " This way, if user performs search in non-normal mode,
                    " we don't clobber it
                    " (we could consider showing index in visual when noshowmode is set).
        "
        " STAR COMMANDS OVERRIDE
                " One Vim's quirk is that '*' and '#' commands silently ignore smartcase  option.
                " This is not detectable,
                    " which makes it impossible to count number of  matches after 'star' commands correctly.
                " Instead of hacking around this problem,
                    " we provide our own implementations  of star commands.
                    " Additional advantage is that their case-sensitiveness can  be controlled with a new option.
        "
        " CACHING
            " To improve efficiency, we cache results of last counting.
            " This makes 'n'  super fast.
            " We only cache linewise counts,
            " and in-line part is always  recalculated.
            " This prevents counting error from building up after multiple  searches
            " if in-line count was imprecise (which happens with regex searches).
